#ifndef ZPP_DEVICE_HPP
#define ZPP_DEVICE_HPP

#include <type_traits>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zpp/assert.hpp>
#include <zpp/error.hpp>
#include <zpp/preprocessor.hpp>
namespace zpp {

template <typename TDerived> struct DeviceBase : protected device {
public:
  static auto &get_instance(const char *device_name) {
    return const_cast<TDerived &>(
        *reinterpret_cast<TDerived *>(device_get_binding(device_name)));
  }
  static auto DeviceInit(const device *dev) -> int {
    auto self = const_cast<TDerived *>(reinterpret_cast<const TDerived *>(dev));
    auto ret = self->Init();
    if constexpr (std::is_same_v<decltype(ret), int>) {
      return ret;
    } else {
      return static_cast<int>(ret.code());
    }
  }
  static auto &get_instance(const device *dev) {
    return const_cast<TDerived &>(reinterpret_cast<const TDerived &>(*dev));
  }

protected:
  DeviceBase() = default;
  DeviceBase(const DeviceBase &) = delete;
  DeviceBase(const DeviceBase &&) = delete;
  DeviceBase &operator=(const DeviceBase &) = delete;
  DeviceBase &operator=(const DeviceBase &&) = delete;
  auto &GetConfig() const {
    return *(reinterpret_cast<const TDerived::Config *>(this->config));
  }

  auto &GetData() {
    return *const_cast<TDerived::Data *>(
        reinterpret_cast<const TDerived::Data *>(this->data));
  };
};

} // namespace zpp

#endif

#define ZPP_DT_NODE(id) DEVICE_DT_GET(DT_NODELABEL(id))
#define ZPP_DEVICE(class_name)                                                 \
  class class_name : public zpp::DeviceBase<class_name>
#define ZPP_DEVICE_GET_BY_NODE(class_name, node_id)                            \
  const_cast<class_name &>(reinterpret_cast<const class_name &>(               \
      *DEVICE_DT_GET(DT_NODELABEL(node_id))))
#define ZPP_DEVICE_DEFINE(class_name)                                          \
  ZPP_SINGLETON(class_name);                                                   \
  static auto get_instance(const char *name) -> const class_name & {           \
    auto p = reinterpret_cast<const class_name *>(device_get_binding(name));   \
    ZPP_ASSERT_FALSE(p == nullptr);                                            \
    return *p;                                                                 \
  }
